package com.nexus.plugin.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.nexus.core.properties.ExternalPropertySourceFactory;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;


/**
 * Redis配置类
 * 配置RedisTemplate序列化方式及缓存相关设置
 *
 * @Date 2025/7/30 10:27
 * @Author luzhengning
 **/
@Configuration
@EnableCaching
@org.springframework.context.annotation.PropertySource(
        name = "redis.properties",
        value = "classpath*:redis-pro.properties",encoding = "UTF8",
        factory = ExternalPropertySourceFactory.class,
        ignoreResourceNotFound = true
)
public class RedisConfig {

    /**
     * 配置RedisTemplate
     * 自定义序列化方式，解决默认JDK序列化效率低、可读性差的问题
     *
     * @Date 2025/7/30 10:57
     * @Author luzhengning
     **/
    @Bean
    @DependsOn("redisObjectMapper")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper) {
        // 创建RedisTemplate对象
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 设置Redis连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 配置Jackson2JsonRedisSerializer序列化器
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(redisObjectMapper(), Object.class);

        // 设置key序列化方式为String（推荐，避免二进制存储）
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 设置value序列化方式为Jackson JSON
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // 设置Hash结构的key序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        // 设置Hash结构的value序列化
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        // 初始化参数设置
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 配置Redis专用的ObjectMapper
     * 解决序列化时的类型信息丢失、日期处理等问题
     *
     * @Date 2025/7/30 10:59
     * @Author luzhengning
     **/
    @Bean
    public ObjectMapper redisObjectMapper() {
        ObjectMapper om = new ObjectMapper();

        // 1. 忽略未知属性，提高兼容性
        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // 2. 设置可见性，允许访问所有字段（包括private）
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

        // 3. 开启类型信息存储（关键！解决反序列化时的类型转换问题）
        // 存储格式：{"@class":"com.example.User","id":1,"name":"test"}
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        // 4. 忽略null值字段
        om.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        // 5. 支持Java 8日期时间类型（LocalDateTime等）
        om.registerModule(new JavaTimeModule());
        // 关闭日期时间的时间戳序列化（使用ISO格式）
        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        return om;
    }

    /**
     * （可选）配置缓存管理器
     * 自定义缓存过期时间等属性
     *
     * @Date 2025/7/30 10:57
     * @Author luzhengning
     **/
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory, ObjectMapper redisObjectMapper) {
        // 配置JSON序列化器
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(redisObjectMapper, Object.class);
        // 使用Redis缓存配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                // 设置默认缓存过期时间（30分钟）
                .entryTtl(Duration.ofMinutes(30))
                // 配置key序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 配置value序列化
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSerializer))
                // 不缓存null值
                .disableCachingNullValues();

        // 可以为不同缓存名称设置不同过期时间
        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
        //cacheConfigurations.put("userCache", config.entryTtl(Duration.ofHours(1))); // 用户缓存1小时
        //cacheConfigurations.put("productCache", config.entryTtl(Duration.ofMinutes(10))); // 商品缓存10分钟

        return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).withInitialCacheConfigurations(cacheConfigurations).build();
    }

    /**
     * 返回一个KeyGenerator对象
     *
     * @Date 2025/7/30 10:56
     * @Author luzhengning
     **/
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            // 创建一个StringBuilder对象
            StringBuilder sb = new StringBuilder();
            // 将目标对象的类名添加到StringBuilder对象中
            sb.append(target.getClass().getName());
            // 将方法名添加到StringBuilder对象中
            sb.append(method.getName());
            // 遍历参数列表，将每个参数的字符串形式添加到StringBuilder对象中
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            // 将StringBuilder对象转换为字符串并返回
            return sb.toString();
        };
    }
}